package com.um.weixin.pay.service.impl;

import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.json.JSONUtil;
import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
import com.github.binarywang.wxpay.bean.notify.WxPayNotifyV3Result;
import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyV3Result;
import com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
import com.github.binarywang.wxpay.bean.result.WxPayRefundV3Result;
import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result;
import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.util.RequestUtils;
import com.um.weixin.pay.service.WxBasePayService;
import com.um.weixin.pay.enums.WxRefundStatus;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.Optional;

/**
 * @author 邓南辉
 * 日期 2023/5/11
 * 版权所有 广州优匠科技有限公司
 */
@Service
@Slf4j
public class WxBasePayServiceImpl implements WxBasePayService {

    @Autowired
    WxPayService wxPayService;


    /**
     * JSAPI下单
     *
     * @param outTradeNo
     * @param openId
     * @param amount
     * @param description
     * @param notifyUrl
     * @return
     * @throws WxPayException
     */
    @Override
    public WxPayUnifiedOrderV3Result.JsapiResult createOrderByJsapi(String outTradeNo, String openId, Integer amount,
                                                                    String description, String notifyUrl) throws WxPayException {
        return createOrderByJsapi(outTradeNo, openId, amount, description, notifyUrl, false);
    }

    /**
     * JSAPI下单
     *
     * @param outTradeNo
     * @param openId
     * @param amount
     * @param description
     * @param notifyUrl
     * @param isProfitSharing
     * @return
     * @throws WxPayException
     */
    @Override
    public WxPayUnifiedOrderV3Result.JsapiResult createOrderByJsapi(String outTradeNo, String openId, Integer amount,
                                                                    String description, String notifyUrl, boolean isProfitSharing) throws WxPayException {
        Date timeExpire = DateUtil.date().offset(DateField.MINUTE, 30);
        return createOrderByJsapi(outTradeNo, openId, amount, description, notifyUrl, isProfitSharing, timeExpire);
    }

    /**
     * JSAPI下单
     *
     * @param outTradeNo
     * @param openId
     * @param amount
     * @param description
     * @param notifyUrl
     * @param isProfitSharing
     * @param timeExpire
     * @return
     * @throws WxPayException
     */
    @Override
    public WxPayUnifiedOrderV3Result.JsapiResult createOrderByJsapi(String outTradeNo, String openId, Integer amount,
                                                                    String description, String notifyUrl, boolean isProfitSharing, Date timeExpire) throws WxPayException {
        WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
        request.setOutTradeNo(outTradeNo);
        request.setDescription(description);
        request.setNotifyUrl(notifyUrl);
        WxPayUnifiedOrderV3Request.Payer payer = new WxPayUnifiedOrderV3Request.Payer();
        payer.setOpenid(openId);
        request.setPayer(payer);
        WxPayUnifiedOrderV3Request.Amount amountObj = new WxPayUnifiedOrderV3Request.Amount();
        amountObj.setTotal(amount);
        request.setAmount(amountObj);
        WxPayUnifiedOrderV3Request.SettleInfo settleInfo = new WxPayUnifiedOrderV3Request.SettleInfo();
        settleInfo.setProfitSharing(isProfitSharing);
        request.setSettleInfo(settleInfo);
        request.setTimeExpire(DateUtil.format(timeExpire, DatePattern.UTC_WITH_XXX_OFFSET_PATTERN));

        return createOrder(TradeTypeEnum.JSAPI, request);
    }

    /**
     * 调用统一下单接口，并组装生成支付所需参数对象.
     *
     * @param <T>       请使用{@link com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result}里的内部类或字段
     * @param tradeType the trade type
     * @param request   统一下单请求参数
     * @return 返回 {@link com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result}里的内部类或字段
     * @throws WxPayException the wx pay exception
     */
    @Override
    public <T> T createOrder(TradeTypeEnum tradeType, WxPayUnifiedOrderV3Request request) throws WxPayException {
        return wxPayService.createOrderV3(tradeType, request);
    }

    /**
     * 申请退款
     *
     * @param outTradeNo
     * @param outRefundNo
     * @param payAmount
     * @param refundAmount
     * @return
     */
    @Override
    public WxPayRefundV3Result refundOrder(String outTradeNo, String outRefundNo, Integer payAmount, Integer refundAmount) throws WxPayException {
        WxPayRefundV3Request request = new WxPayRefundV3Request();
        request.setOutTradeNo(outTradeNo);
        request.setOutRefundNo(outRefundNo);
        WxPayRefundV3Request.Amount amount = new WxPayRefundV3Request.Amount();
        amount.setTotal(payAmount);
        amount.setRefund(refundAmount);
        amount.setCurrency("CNY"); //币种，目前仅仅支持"CNY"
        request.setAmount(amount);

        WxPayRefundV3Result wxPayRefundV3Result = refundOrder(request);
        log.info("微信申请退款结果：", JSONUtil.toJsonStr(wxPayRefundV3Result));

        // TODO 改为回调通知
        if(WxRefundStatus.ABNORMAL.name().equals(wxPayRefundV3Result.getStatus()) ||
                WxRefundStatus.CLOSED.name().equals(wxPayRefundV3Result.getStatus())){
            throw new WxPayException(String.format("退款状态[%s]错误", wxPayRefundV3Result.getStatus()));
        }

        return wxPayRefundV3Result;
    }

    /**
     * 申请退款
     *
     * @param outTradeNo
     * @param outRefundNo
     * @param payAmount
     * @param refundAmount
     * @param notifyUrl
     * @return
     */
    @Override
    public WxPayRefundV3Result refundOrder(String outTradeNo, String outRefundNo, Integer payAmount, Integer refundAmount, String notifyUrl) throws WxPayException {
        WxPayRefundV3Request request = new WxPayRefundV3Request();
        request.setOutTradeNo(outTradeNo);
        request.setOutRefundNo(outRefundNo);
        WxPayRefundV3Request.Amount amount = new WxPayRefundV3Request.Amount();
        amount.setTotal(payAmount);
        amount.setRefund(refundAmount);
        amount.setCurrency("CNY"); //币种，目前仅仅支持"CNY"
        request.setAmount(amount);
        request.setNotifyUrl(notifyUrl);

        WxPayRefundV3Result wxPayRefundV3Result = refundOrder(request);
        log.info("微信申请退款结果：", JSONUtil.toJsonStr(wxPayRefundV3Result));

        return wxPayRefundV3Result;
    }

    /**
     * 申请退款
     *
     * @param outTradeNo
     * @param outRefundNo
     * @param payAmount
     * @param refundAmount
     * @param notifyUrl
     * @return
     */
    @Override
    public WxPayRefundV3Result refundOrder(String outTradeNo, String transactionId, String outRefundNo, Integer payAmount, Integer refundAmount, String notifyUrl) throws WxPayException {
        WxPayRefundV3Request request = new WxPayRefundV3Request();
        request.setOutTradeNo(outTradeNo);
        request.setTransactionId(transactionId);
        request.setOutRefundNo(outRefundNo);
        WxPayRefundV3Request.Amount amount = new WxPayRefundV3Request.Amount();
        amount.setTotal(payAmount);
        amount.setRefund(refundAmount);
        amount.setCurrency("CNY"); //币种，目前仅仅支持"CNY"
        request.setAmount(amount);
        request.setNotifyUrl(notifyUrl);

        WxPayRefundV3Result wxPayRefundV3Result = refundOrder(request);
        log.info("微信申请退款结果：", JSONUtil.toJsonStr(wxPayRefundV3Result));

        return wxPayRefundV3Result;
    }

    /**
     * <pre>
     * 申请退款API（支持单品）.
     * 详见 <a href="https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_9.shtml">https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_9.shtml</a>
     *
     * 应用场景
     * 当交易发生之后一年内，由于买家或者卖家的原因需要退款时，卖家可以通过退款接口将支付金额退还给买家，微信支付将在收到退款请求并且验证成功之后，将支付款按原路退还至买家账号上。
     *
     * 注意：
     * 1、交易时间超过一年的订单无法提交退款
     * 2、微信支付退款支持单笔交易分多次退款（不超50次），多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。申请退款总金额不能超过订单金额。 一笔退款失败后重新提交，请不要更换退款单号，请使用原商户退款单号
     * 3、错误或无效请求频率限制：6qps，即每秒钟异常或错误的退款申请请求不超过6次
     * 4、每个支付订单的部分退款次数不能超过50次
     * 5、如果同一个用户有多笔退款，建议分不同批次进行退款，避免并发退款导致退款失败
     * 6、申请退款接口的返回仅代表业务的受理情况，具体退款是否成功，需要通过退款查询接口获取结果
     * 7、一个月之前的订单申请退款频率限制为：5000/min
     *
     * 接口地址
     * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds
     * </pre>
     *
     * @param request 请求对象
     * @return 退款操作结果 wx pay refund result
     * @throws WxPayException the wx pay exception
     */
    @Override
    public WxPayRefundV3Result refundOrder(WxPayRefundV3Request request) throws WxPayException {
        return wxPayService.refundV3(request);
    }

    @Override
    public WxPayNotifyV3Result parseOrderNotify(HttpServletRequest request) throws WxPayException {

        String timestamp = request.getHeader("Wechatpay-Timestamp");
        Optional.ofNullable(timestamp).orElseThrow(() -> new RuntimeException("时间戳不能为空"));

        String nonce = request.getHeader("Wechatpay-Nonce");
        Optional.ofNullable(nonce).orElseThrow(() -> new RuntimeException("nonce不能为空"));

        String serialNo = request.getHeader("Wechatpay-Serial");
        Optional.ofNullable(serialNo).orElseThrow(() -> new RuntimeException("serialNo不能为空"));

        String signature = request.getHeader("Wechatpay-Signature");
        Optional.ofNullable(signature).orElseThrow(() -> new RuntimeException("signature不能为空"));

        log.info("请求头参数为：timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);

        WxPayNotifyV3Result wxPayNotifyV3Result = wxPayService.parseOrderNotifyV3Result(RequestUtils.readData(request),
                new SignatureHeader(timestamp, nonce, signature, serialNo));

        return wxPayNotifyV3Result;
    }

    /**
     * 解析退款结果通知
     * 详见https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_16&index=9
     *
     * @param request
     * @return the wx pay refund notify result
     * @throws WxPayException the wx pay exception
     */
    @Override
    public WxPayRefundNotifyV3Result parseRefundNotifyV3Result(HttpServletRequest request) throws WxPayException {
        String timestamp = request.getHeader("Wechatpay-Timestamp");
        String nonce = request.getHeader("Wechatpay-Nonce");
        String serialNo = request.getHeader("Wechatpay-Serial");
        String signature = request.getHeader("Wechatpay-Signature");

        WxPayRefundNotifyV3Result wxPayRefundNotifyV3Result = null;
        try {
            wxPayRefundNotifyV3Result = wxPayService.parseRefundNotifyV3Result(RequestUtils.readData(request),
                    new SignatureHeader(timestamp, nonce, signature, serialNo));
        } catch (WxPayException e) {
            log.error("解析微信退款回调错误", e);
        }
        log.info("微信退款回调通知\n{}", JSONUtil.toJsonStr(wxPayRefundNotifyV3Result));

        return wxPayRefundNotifyV3Result;
    }
}
